/*
 * Copyright 2009 The Brui Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */

package com.googlecode.brui.beans;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ResourceBundle;

import com.googlecode.brui.annotation.TranslationKey;

/**
 * A property from a bean instance. This uses a
 * <code>java.beans.PropertyDescriptor</code> to provide convenient
 * manipulation of a property on a bean. It also provides internationalisation
 * of the property display name.
 */
public abstract class Property {

	private final PropertyDescriptor propertyDescriptor;

	private final Object javaBean;

	/**
	 * Create a {@link Property} as described by the given
	 * <code>PropertyDescriptor</code>, and is bound to the given bean. The
	 * property descriptor must be relevant for the type of the bean.
	 * 
	 * @param propertyDescriptor the property descriptor
	 * @param javaBean the bean instance the property is bound to.
	 * @throws NullPointerException if any arguments are null.
	 * @throws IllegalArgumentException if the property descriptor is not valid
	 *         for the type of the bean.
	 */
	Property(final PropertyDescriptor propertyDescriptor, final Object javaBean) {
		if (propertyDescriptor == null) {
			throw new NullPointerException();
		}
		if (javaBean == null) {
			throw new NullPointerException();
		}
		// TODO check that the property descriptor is valid for the bean's type
		this.propertyDescriptor = propertyDescriptor;
		this.javaBean = javaBean;
	}

	protected final PropertyDescriptor getPropertyDescriptor() {
		return propertyDescriptor;
	}

	final Object getJavaBean() {
		return javaBean;
	}

	/**
	 * Get the name of the property.
	 * 
	 * @return the name of the property.
	 */
	public final String getName() {
		return propertyDescriptor.getName();
	}

	/**
	 * @return the type of the property.
	 */
	public final Class<?> getType() {
		return propertyDescriptor.getPropertyType();
	}

	/**
	 * @return the value of the property.
	 */
	public final Object getValue() {
		try {
			return getPropertyDescriptor().getReadMethod().invoke(getJavaBean());
		} catch (final IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (final InvocationTargetException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * @param value the new value for the property.
	 */
	public final void setValue(final Object value) {
		// FIXME this is broken for primitive properties
		if (value != null && !getType().isAssignableFrom(value.getClass())) {
			throw new IllegalArgumentException("Cannot assign bean property '" + getName() + "' of type '" + getType().getName()
					+ "' a value of type '" + value.getClass().getName() + "'.");
		}
		try {
			getPropertyDescriptor().getWriteMethod().invoke(getJavaBean(), value);
		} catch (final IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (final InvocationTargetException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Get the internationalised display name of a bean property.
	 * <p>
	 * A bean property name may be internationalised by using the
	 * {@link TranslationKey} annotation. If the annotation is not present on the
	 * bean property then the name of the property is used as the translation
	 * key. Translations are obtained from the <code>brui-messages</code>
	 * bundle. If no translation is found then the property name will be used as
	 * the description.
	 * 
	 * @return the internationalised name of the property.
	 */
	public final String getDisplayName() {
		// FIXME this isn't working!
		final String key = getTranslataionKey();
		final ResourceBundle bundle = getBruiResourceBundle();
		// TODO allow translation bundle to be null for non i18n apps
		if (key != null && bundle.containsKey(key)) {
			return bundle.getString(key);
		} else if (bundle.containsKey(getName())) {
			return bundle.getString(getName());
		} else {
			return getName();
		}
	}

	protected final String getTranslataionKey() {
		return getField().getAnnotation(TranslationKey.class).value();
	}

	protected final Field getField() {
		try {
			return getJavaBean().getClass().getField(getName());
		} catch (final SecurityException e) {
			// TODO throw a more specific exception
			throw new RuntimeException(e);
		} catch (final NoSuchFieldException e) {
			throw new RuntimeException(e);
		}
	}

	// TODO this bundle must be accessible globally
	private ResourceBundle getBruiResourceBundle() {
		// TODO make the bundle name configurable
		return ResourceBundle.getBundle("brui-messages");
	}
}
